﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using EventHandler = Splendor_Server.script.logic.msg.EventHandler;

namespace Splendor_Server.script.net
{
    public static class NetManager
    {
        //监听Socket
        private static Socket listenfd;

        //客户端Socket及状态信息
        public static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();

        //Select的检查列表
        private static List<Socket> checkRead = new List<Socket>();

        //ping间隔
        public static long pingInterval = 30;

        public static void StartLoop(int listenPort)
        {
            listenfd = new Socket(AddressFamily.InterNetwork,
                SocketType.Stream, ProtocolType.Tcp);

            IPAddress ipAdr = IPAddress.Parse("0.0.0.0");
            IPEndPoint ipEp = new IPEndPoint(ipAdr, listenPort);
            listenfd.Bind(ipEp);

            listenfd.Listen(0);
            Console.WriteLine("[{0}]服务器启动成功", GetNowTime());

            //程序主循环
            while (true)
            {
                ResetCheckRead(); //重置checkRead
                Socket.Select(checkRead, null, null, 1000);

                //检查可读对象
                for (int i = checkRead.Count - 1; i >= 0; i--)
                {
                    Socket s = checkRead[i];
                    if (s == listenfd)
                    {
                        ReadListenfd(s);
                    }
                    else
                    {
                        ReadClientfd(s);
                    }
                }

                //启用定时器
                Timer();
            }
        }

        //填充checkRead列表
        private static void ResetCheckRead()
        {
            checkRead.Clear();
            checkRead.Add(listenfd);
            foreach (ClientState s in clients.Values)
            {
                checkRead.Add(s.socket);
            }
        }

        //读取Listenfd
        private static void ReadListenfd(Socket listenfd)
        {
            try
            {
                Socket clientfd = listenfd.Accept();
                Console.WriteLine("[{0}]Accept: {1}", GetNowTime(), clientfd.RemoteEndPoint);
                ClientState state = new ClientState {socket = clientfd, lastPingTime = GetTimeStamp()};
                clients.Add(clientfd, state);
            }
            catch (SocketException ex)
            {
                Console.WriteLine("[{0}]AcceptError: {1}", GetNowTime(), ex);
            }
        }

        //读取Clientfd
        private static void ReadClientfd(Socket clientfd)
        {
            ClientState state = clients[clientfd];
            ByteArray readBuff = state.readBuff;
            //接收
            int count;
            //缓冲区不够，清除，若依旧不够，只能返回
            //当单条协议超过缓冲区长度时会发生
            if (readBuff.remain <= 0)
            {
                OnReceiveData(state);
                readBuff.MoveBytes();
            }

            if (readBuff.remain <= 0)
            {
                Console.WriteLine("[{0}]ReceiveFail: Maybe msg length > buff capacity", GetNowTime());
                Close(state);
                return;
            }

            try
            {
                Console.WriteLine("[{0}]Receive: {1}", GetNowTime(), clientfd.RemoteEndPoint);
                count = clientfd.Receive(readBuff.bytes, readBuff.writeIdx, readBuff.remain, 0);
            }
            catch (SocketException ex)
            {
                Console.WriteLine("[{0}]ReceiveError: {1}", GetNowTime(), ex);
                Close(state);
                return;
            }

            //客户端关闭
            if (count <= 0)
            {
                Console.WriteLine("[{0}]ReceiveClose: {1}", GetNowTime(), clientfd.RemoteEndPoint);
                Close(state);
                return;
            }

            //消息处理
            readBuff.writeIdx += count;
            //处理二进制消息
            OnReceiveData(state);
            //移动缓冲区
            readBuff.CheckAndMoveBytes();
        }

        //关闭连接
        public static void Close(ClientState client)
        {
            // MethodInfo mei = typeof(EventHandler).GetMethod("OnDisconnect");
            // object[] ob = {client};
            // mei.Invoke(null, ob);
            EventHandler.OnDisconnect(client);
            client.socket.Close();
            clients.Remove(client.socket);
        }

        //数据处理
        private static void OnReceiveData(ClientState state)
        {
            ByteArray readBuff = state.readBuff;
            //消息长度
            if (readBuff.length <= 2)
            {
                return;
            }

            //消息体长度
            int readIdx = readBuff.readIdx;
            byte[] bytes = readBuff.bytes;
            Int16 bodyLength = (Int16) ((bytes[readIdx + 1] << 8) | bytes[readIdx]);
            if (readBuff.length < bodyLength)
            {
                return;
            }

            readBuff.readIdx += 2;
            //解析协议名
            string protoName = MsgBase.NameDecode(readBuff.bytes, readBuff.readIdx, out var nameCount);
            if (protoName == "")
            {
                Console.WriteLine("[{0}]ReceiveFail: OnReceiveData MsgBase.DecodeName", GetNowTime());
                Close(state);
                return;
            }

            readBuff.readIdx += nameCount;
            //解析协议体
            int bodyCount = bodyLength - nameCount;
            if (bodyCount <= 0)
            {
                Console.WriteLine("[{0}]ReceiveFail: OnReceiveData bodyCount <=0", GetNowTime());
                Close(state);
                return;
            }

            MsgBase msgBase = MsgBase.Decode(protoName, readBuff.bytes, readBuff.readIdx, bodyCount);
            readBuff.readIdx += bodyCount;
            readBuff.CheckAndMoveBytes();
            //分发消息
            MethodInfo mi = typeof(logic.msg.MsgHandler).GetMethod(protoName);
            object[] o = {state, msgBase};

            if (mi != null)
            {
                mi.Invoke(null, o);
            }
            else
            {
                Console.WriteLine("[{0}]ReceiveFail: {1} OnReceiveData Invoke", GetNowTime(), protoName);
            }

            //继续读取消息
            if (readBuff.length > 2)
            {
                OnReceiveData(state);
            }
        }

        //发送
        public static void Send(ClientState cs, MsgBase msg)
        {
            //状态判断
            if (cs == null)
            {
                return;
            }

            if (!cs.socket.Connected)
            {
                return;
            }

            //数据编码
            byte[] nameBytes = MsgBase.NameEncode(msg);
            byte[] bodyBytes = MsgBase.Encode(msg);
            int len = nameBytes.Length + bodyBytes.Length;
            byte[] sendBytes = new byte[2 + len];
            //组装长度
            sendBytes[0] = (byte) (len % 256);
            sendBytes[1] = (byte) (len / 256);
            //组装名字
            Array.Copy(nameBytes, 0, sendBytes, 2, nameBytes.Length);
            //组装消息体
            Array.Copy(bodyBytes, 0, sendBytes, 2 + nameBytes.Length, bodyBytes.Length);
            //为简化代码，不设置回调 TODO Why?
            try
            {
                cs.socket.BeginSend(sendBytes, 0, sendBytes.Length, 0, null, null);
                Console.WriteLine("[{0}]Send: TO-{1} {2}", GetNowTime(), cs.socket.RemoteEndPoint,
                    System.Text.Encoding.UTF8.GetString(sendBytes));
            }
            catch (SocketException ex)
            {
                Console.WriteLine("[{0}]SendError: TO-{1} {2}", GetNowTime(), cs.socket.RemoteEndPoint, ex);
            }
        }

        public static void SendAll(MsgBase msg)
        {
            foreach (ClientState client in clients.Values)
                Send(client, msg);
        }

        //定时器
        private static void Timer()
        {
            // MethodInfo mei = typeof(EventHandler).GetMethod("OnTimer");
            // object[] ob = { };
            // mei.Invoke(null, ob);
            EventHandler.OnTimer();
        }

        //获取时间戳 (单位：秒)
        public static long GetTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds);
        }

        public static string GetNowTime()
        {
            return DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss:ffff");
        }
    }
}